package com.jappyframework;

import static org.junit.Assert.fail;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * A bunch of utility functions to perform some automatic testing. Kind of
 * things we do here are 1) Check for null Arguments. Exception must be thrown.
 * 2) Check for empty arguments for String. Exception must be thrown.
 * 
 * TODO:- Make things more robust by permuting the null/empty values.
 * 
 * @author harsh
 */
public class AutoTestUtil {

	/**
	 * 
	 * @param c
	 */
	public static void testConstructorsWithNullArguments(Class<?> c) {
		for (Constructor<?> cons : c.getConstructors()) {
			if (isOnlyPrimitive(cons.getParameterTypes())) {
				continue;
			}
			Object[] args = generateNullArguments(cons.getParameterTypes());
			try {
				cons.newInstance(args);
				fail("Constructor " + cons.getName() + " Should have failed");
			} catch (Exception e) {
			}
		}
	}

	public static void testMethodWithNullArguments(
			Class<?> c,
			Object instance,
			String methodName) {
		for (Method method : c.getDeclaredMethods()) {
			if (method.getName().equals(methodName)) {
				if (isOnlyPrimitive(method.getParameterTypes())) {
					continue;
				}
				Object[] args = generateNullArguments(method.getParameterTypes());
				try {
					method.invoke(instance, args);
					fail("Method " + methodName + " should have thrown exception");
				} catch (Exception e) {
				}
			}
		}
	}
	
	/**
	 * Call this if you want to test a method which has only String+primitives as arguments.
	 * This will try to crash the method by passing empty.
	 * 
	 * TODO: Add support when objects are present.
	 * 
	 * @param c
	 * @param instance
	 * @param methodName
	 */
	public static void testMethodWithEmptyArguments(
			Class<?> c,
			Object instance,
			String methodName) {
		for (Method method : c.getDeclaredMethods()) {
			if (method.getName().equals(methodName)) {
				if (isOnlyPrimitive(method.getParameterTypes())) {
					continue;
				}
				Object[] args = generateEmptyArguments(method.getParameterTypes());
				try {
					method.invoke(instance, args);
					fail("Method " + methodName + " should have thrown exception");
				} catch (Exception e) {
				}
			}
		}
	}

	/**
	 * Return if a bunch of arguments are ONLY primitive. In this case we may
	 * decide to not do the test.
	 * 
	 * @param paraTypes
	 * @return
	 */
	private static boolean isOnlyPrimitive(Class<?>[] paraTypes) {
		boolean ret = true;
		for (Class<?> c : paraTypes) {
			ret &= c.isPrimitive();
		}
		return ret;
	}
	
	/**
	 * 
	 * @param paraTypes
	 * @return
	 */
	private static Object[] generateNullArguments(Class<?>[] paraTypes) {
		List<Object> retValue = new ArrayList<Object>();

		for (Class<?> c: paraTypes) {
			if (!c.isPrimitive()) {
				retValue.add(null);
				continue;
			}
			retValue.add(getDefaultPrimitiveValue(c));
		}
		return retValue.toArray();
	}
	
	/**
	 * Generate empty arguments. Only strings and/or primitives are accepted. 
	 * 
	 * @param paraTypes
	 * @return
	 */
	private static Object[] generateEmptyArguments(Class<?>[] paraTypes) {
		List<Object> retValue = new ArrayList<Object>();
		for (Class<?> c: paraTypes) {
			if (!c.isPrimitive()) {
				retValue.add("");
				continue;
			}
			retValue.add(getDefaultPrimitiveValue(c));
		}
		return retValue.toArray();	
	}
	
	private static Object getDefaultPrimitiveValue(Class<?> c) {
		assert c.isPrimitive();
		if (c.equals(Boolean.TYPE)) {
			return true;
		}
		if (c.equals(Short.TYPE)) {
			return 0;
		}
		if (c.equals(Integer.TYPE)) {
			return 0;
		}
		if (c.equals(Double.TYPE)) {
			return 0.0;
		}
		if (c.equals(Float.TYPE)) {
			return 0.0f;
		}
		if (c.equals(Byte.TYPE)) {
			return 0;
		}
		if (c.equals(Character.TYPE)) {
			return 'a';
		}
		if (c.equals(Long.TYPE)) {
			return 0;
		}
		return null;
	}

}
